import { Application, scheduleJob, Channel, BackendSession, getLogger } from "pinus";
import { UserService } from "../../services/userService";
import { RecordService } from "../../services/recordService";
import { crop } from "../../util/object";
import { randomNumber, randResult, smooth } from "./algorithm"
import { date2Number } from "../../util/time";
import controlConfig, { ControlConfig } from "./controlConfig";
import { cancelJob } from "pinus-scheduler";
import gameConfig, { GameConfig } from "./gameConfig";
import { promisify } from "util";
import * as https from "https";
import { RandomManager } from "../../util/random";
import { MinesRole } from "../../consts/code";
const logger = getLogger("aviator")

export enum STATUS {
    /** 等待 */
    WAIT,
    /** 开始 */
    GAME,
    /** 结束 */
    RESULT,
}

let aviator: Aviator = null

// 统一创建对象接口
export default function (app: Application) {
    if (aviator === null) {
        aviator = new Aviator(app)
    }
    return aviator
}

export class Aviator {

    /** 随机生成的棋盘 0是钻石，1是地雷 */
    private _MinesMapDatas: { userId: number, map: number[] }[]
    /** 当前游戏中的棋盘 -1未点开，0是钻石，1是地雷 */
    private _MinesNowMapDatas: { userId: number, map: number[] }[]
    // 游戏状态
    private _roundId: number;
    private _status: STATUS;
    private _statusTime: number;
    private _running: boolean;
    private _syncPoints: number[];
    private _syncIndex: number;
    private _statusJob: number;
    private _userInfos: { userId: number, username: string, coin: number, offline: boolean, channelId: number, currency?: string, currencyNum?: string, expand: string }[]
    private _betInfos: { userId: number, index: number, betCount: number, multiple: number, auto: boolean, cashOut: boolean, betTime: Date, finishTime: Date, beforCoin: number, afterCoin: number, currency?: string, minesNum?: number }[];                                //手动下注
    private _robotBetInfos: { userId: number, username: string, index: number, betCount: number, multiple: number, auto: boolean, cashOut: boolean, betTime: Date, finishTime: Date, beforCoin: number, afterCoin: number }[]
    private _history: number[]
    // 加载数据的时候存在异步情况，为防止loading过程中玩家offline
    private _loading: number[];
    private _unloading: number[];
    private _updating: number[];
    private _channel: Channel
    private _observer: Channel
    private _userService: UserService
    private _recordService: RecordService
    private _controlConfig: ControlConfig
    private _gameConfig: GameConfig
    private _setting: { notice: boolean }
    constructor(private app: Application) {
        this._channel = app.channelService.getChannel("game_member", true)
        this._observer = app.channelService.getChannel("observer_member", true)
        this._userService = app.get('userService')
        this._recordService = app.get('recordService')
        this._setting = app.get('setting')

        this._controlConfig = controlConfig(app)
        this._gameConfig = gameConfig(app)
        //初始化
        this._userInfos = []
        this._loading = []
        this._updating = []
        this._betInfos = []
        this._robotBetInfos = []
        this._history = []
        this._unloading = []
        this._MinesMapDatas = []
        this._MinesNowMapDatas = []

    }
    public run() {
        if (this._running) {
            return
        }
        this._running = true
        this.onStart()
    }
    public get running() {
        return this._running
    }
    public stop() {
        if (!this._running) {
            return
        }
        this._running = false
    }
    // 广播消息
    public async sendToAll(route: string, msg: any) {
        this._observer.apushMessage(route, msg)
        if (route === 'bet') {
            msg.username = msg.username.replace(/(\w{1}).*(\w{1})/, "$1****$2")
        }
        return this._channel.apushMessage(route, msg)
    }
    // 发送数据给玩家
    public async sendToUser(userId: number, route: string, msg: any) {
        const member = this._channel.getMember(userId.toString())
        if (!member) {
            return
        }
        return this.app.channelService.apushMessageByUids(route, msg, [member])
    }
    public canReconnect(userId: number): boolean {
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (!userInfo) {
            return false
        }
        return userInfo.offline
    }
    public unloading(userId: number): boolean {
        return this._unloading.includes(userId)
    }
    public loading(userId: number): boolean {
        return this._loading.includes(userId)
    }
    public updating(userId: number): boolean {
        return this._updating.includes(userId)
    }
    public reconnect(userId: number) {
        const connectors = this.app.getServersByType('connector')
        const userInfo = this._userInfos.find(e => e.userId === userId)
        userInfo.offline = false
        this._channel.add(userId.toString(), connectors[0].id)
    }
    // 玩家enter  如果这里报错  检查下用户锁字段 is_lock  进入时会加锁
    public async enter(userId: number): Promise<boolean> {
        const connectors = this.app.getServersByType('connector')
        try {
            this._loading.push(userId)
            const userModal = await this._userService.loadUserInfo(userId)
            if (!this._loading.includes(userId)) {
                //解锁玩家数据
                await this._userService.unlockCoinById(userId)
                return false
            }
            const userInfo = {
                userId: userModal.userId,
                username: userModal.username,
                coin: userModal.coin,
                offline: false,
                channelId: userModal.channelId,
                // currency: userModal.currency,
                // currencyNum: userModal.currencyNum,
                expand: userModal.expand
            }
            this._userInfos.push(userInfo)
        } finally {
            const index = this._loading.findIndex(e => e === userId)
            this._loading.splice(index, 1)
        }
        this._channel.add(userId.toString(), connectors[0].id)

        return true
    }

    public async addObserver(observerId: number): Promise<boolean> {
        const controls = this.app.getServersByType('control')
        return this._observer.add(observerId.toString(), controls[0].id)
    }

    public async removeObserver(observerId: number) {
        return this._observer.removeMember(observerId.toString())
    }

    public getContext(userId: number) {
        // 根据状态同步不一样的数据
        let statusData = null
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (this._status === STATUS.RESULT) {
            statusData = {
                leftTime: Date.now() - this._statusTime
            }
        } else if (this._status === STATUS.WAIT) {
            let betList = []
            this._betInfos.forEach(e => {
                const userInfo = this._userInfos.find(e1 => e1.userId === e.userId)
                if (!userInfo) {
                    return
                }
                betList.push({ userId: e.userId, username: userInfo.username.replace(/(\w{1}).*(\w{1})/, "$1****$2"), betCount: e.betCount })
            })
            this._robotBetInfos.forEach(e => {
                betList.push({ userId: e.userId, username: e.username.replace(/(\w{1}).*(\w{1})/, "$1****$2"), betCount: e.betCount })
            })
            statusData = {
                leftTime: Date.now() - this._statusTime,
                betList: betList
            }
        } else if (this._status === STATUS.GAME) {
            let betList = []
            this._betInfos.forEach(e => {
                const userInfo = this._userInfos.find(e1 => e1.userId === e.userId)
                if (!userInfo) {
                    return
                }
                betList.push({ userId: e.userId, username: userInfo.username.replace(/(\w{1}).*(\w{1})/, "$1****$2"), betCount: e.betCount, cashOut: e.cashOut, multiple: e.multiple, index: e.index })
            })
            this._robotBetInfos.forEach(e => {
                betList.push({ userId: e.userId, username: e.username.replace(/(\w{1}).*(\w{1})/, "$1****$2"), betCount: e.betCount, cashOut: e.cashOut, multiple: e.multiple, index: e.index })
            })
            let syncPoint = this._syncPoints.slice(0, this._syncIndex)
            let betInfo = this._betInfos.filter(e => e.userId === userId)
            statusData = {
                betList: betList,
                syncPoint: syncPoint,
                betInfo: betInfo
            }
        }
        return { userInfo: crop(userInfo, 'offline'), history: this._history, status: this._status, statusData: statusData }
    }

    public getContextMines(userId: number) {
        //获取用户数据
        const userInfo = this._userInfos.find(e => e.userId === userId)

        return { userInfo: crop(userInfo, 'offline') }
    }


    // 是否有足够的钱
    public getCoin(userId: number): number {
        const userInfo = this._userInfos.find(e => e.userId === userId)
        return userInfo.coin
    }
    // 当前下注次数
    public betTimes(userId: number) {
        return this._betInfos.filter(e => e.userId === userId).length
    }
    // 获取注单
    public getBet(userId: number, index: number) {
        return this._betInfos.find(e => e.userId === userId && e.index === index)
    }
    // bet
    public bet(userId: number, index: number, betCount: number, auto: boolean, multiple: number): boolean {
        const userInfo = this._userInfos.find(e => e.userId === userId)
        userInfo.coin -= betCount
        this._betInfos.push({
            userId: userId,
            index: index,
            betCount: betCount,
            multiple: auto ? multiple : 0,
            auto: auto,
            cashOut: false,
            betTime: new Date(),
            finishTime: new Date(),
            beforCoin: userInfo.coin,
            afterCoin: 0
        })
        this.sendToAll('bet', { userId: userId, index: index, username: userInfo.username, betCount: betCount })
        return true
    }

    public betMines(userId: number, index: number, betCount: number, mines: number): boolean {
        this.initBet(userId, betCount, mines)
        return true
    }

    /** 初始注单 */
    private initBet(userId: number, betCount: number, mines: number) {
        //游戏开始
        this._status = STATUS.GAME
        this._roundId = date2Number(new Date())
        this._statusTime = Date.now()

        const userInfo = this._userInfos.find(e => e.userId === userId)
        userInfo.coin -= betCount
        //是否存在棋盘
        let mapDataIndex = this._MinesMapDatas.findIndex(e => e.userId === userId)
        //创建更新数据
        if (mapDataIndex >= 0) {
            let mapData = this._MinesMapDatas.find(e => e.userId === userId)
            mapData.map = []
            mapData.map = this.createMinesMap(mines, userId)

            //清空注单
            let userIndex = this._betInfos.findIndex(e => e.userId === userId)
            this._betInfos.splice(userIndex, 1)

            //清空当前棋盘
            let nowMapDataIndex = this._MinesNowMapDatas.findIndex(e => e.userId === userId)
            this._MinesNowMapDatas.splice(nowMapDataIndex, 1)

        } else {
            this._MinesMapDatas.push({
                userId: userId,
                map: this.createMinesMap(mines, userId)
            })
        }

        this._betInfos.push({
            userId: userId,
            index: date2Number(new Date()),
            betCount: betCount,
            multiple: -1,
            auto: false,
            cashOut: false,
            betTime: new Date(),
            finishTime: new Date(),
            currency: userInfo.currency,
            minesNum: mines,
            beforCoin: userInfo.coin,
            afterCoin: 0
        })

        //当前记录棋盘 初始化 
        this._MinesNowMapDatas.push({
            userId: userId,
            map: [
                -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1
            ]
        })
    }


    /** 生成棋盘 */
    public createMinesMap(mines: number, userId: number): Array<number> {
        let mapData = []
        //随机地雷位置(这里取的是下标) 25个格子  例如： [3,5,8]  3个雷   数组里面的是坐标位置
        let minesPos = RandomManager.instance.getRandomByMinMaxList_NotRepeated(0, 24, mines)
        //生成棋盘
        for (let i = 0; i < 25; i++) {
            mapData.push(0) //砖石
        }
        for (let i = 0; i < minesPos.length; i++) {
            mapData[minesPos[i]] = 1
        }
        return mapData
    }


    /** 位置校验  返回是砖石还是地雷 */
    public inspectMapPos(pos: number, userId: number): number {
        //获取系统生成的棋盘
        let mapData = this._MinesMapDatas.find(e => e.userId === userId).map
        let nowMapData = this._MinesNowMapDatas.find(e => e.userId === userId).map
        let result = -1
        for (let i = 0; i < mapData.length; i++) {
            if (pos == i) {
                result = mapData[i]
                //更新用户的当前棋盘
                nowMapData[i] = result
            }
        }
        const betInfo = this._betInfos.find(e => e.userId === userId)

        //如果是地雷  自动结算
        if (result === 1) {
            // betInfo.multiple = 0
            this.cashOutMines(userId, betInfo.index, 0)
            this.EndMine(userId, betInfo.index)
        }
        if (result === 0) {
            //如果钻石被点完了 结算
            let minesNum_map = this._betInfos.find(e => e.userId === userId).minesNum//用户定义的雷数量
            let dNum_nowMap = MinesRole.countNum(nowMapData, 0) //当前棋盘钻石数量
            let seccess = dNum_nowMap + parseInt(minesNum_map + '')
            if (seccess == 25) {
                //通关  自动结算
                this.cashOutMines(userId, betInfo.index, 0)
                this.EndMine(userId, betInfo.index)
            }
        }
        return result
    }

    /** 位置校验  返回输赢及数据 */
    public inspectAutoMapPos(autoClickMap: Array<number>, betNum: number, MinesNum: number, userId) {
        //初始注单
        this.initBet(userId, betNum, MinesNum)

        //生成棋盘
        let sysMap = this._MinesMapDatas.find(e => e.userId === userId).map
        let diamondNum = 0//用户选择的位置钻石数量
        let mNum = 0//用户选择的位置钻石数量
        let nowMapData = this._MinesNowMapDatas.find(e => e.userId === userId).map
        //对比棋盘
        for (let i = 0; i < autoClickMap.length; i++) {
            //用户选择的位置
            if (autoClickMap[i] == 1) {
                //这个位置是否有雷
                if (sysMap[i] == 1) {
                    nowMapData[i] = 1
                    mNum++
                }
                if (sysMap[i] == 0) {
                    nowMapData[i] = 0
                    diamondNum++
                }
            }
        }

        //加减金币
        const betInfo = this._betInfos.find(e => e.userId === userId)
        const userInfo = this._userInfos.find(e => e.userId === userId)
        betInfo.finishTime = new Date()
        const reward = betInfo.betCount * betInfo.multiple
        userInfo.coin += reward
        betInfo.afterCoin = userInfo.coin
        let mapData = this._MinesMapDatas.find(e => e.userId === userId)
        let clickMapData = this._MinesNowMapDatas.find(e => e.userId === userId)

        //如果是地雷  自动结算
        if (mNum > 0) {
            // betInfo.multiple = 0
            this.cashOutMines(userId, betInfo.index, 1)
            this.EndMine(userId, betInfo.index)
        } else {
            //如果钻石被点完了 结算
            let minesNum_map = MinesNum//用户定义的雷数量
            let dNum_nowMap = diamondNum //当前棋盘钻石数量

            betInfo.multiple = MinesRole.get_role(minesNum_map, dNum_nowMap)

            // betInfo.multiple = 0
            this.cashOutMines(userId, betInfo.index, 1)
            this.EndMine(userId, betInfo.index)

        }


        //手动结算返回
        return {
            success: true, index: betInfo.index, multiple: betInfo.multiple,
            cashOut: reward, coin: userInfo.coin, mapData: mapData.map, clickMapData: clickMapData.map, auto: 1//是否自动下注
        }

    }


    public cashOutMines(userId: number, index: number, auto: number) {
        const betInfo = this._betInfos.find(e => e.userId === userId && e.index === index)
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (!betInfo) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        if (betInfo.cashOut) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        if (!userInfo) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        betInfo.cashOut = true
        let minesNum = MinesRole.countNum(this._MinesNowMapDatas.find(e => e.userId === userId).map, 1)
        if (minesNum > 0) {
            betInfo.multiple = 0
        } else {
            let dNum = MinesRole.countNum(this._MinesNowMapDatas.find(e => e.userId === userId).map, 0)
            betInfo.multiple = MinesRole.get_role(betInfo.minesNum, dNum)
        }

        betInfo.finishTime = new Date()
        const reward = betInfo.betCount * betInfo.multiple
        userInfo.coin += reward
        betInfo.afterCoin = userInfo.coin
        let mapData = this._MinesMapDatas.find(e => e.userId === userId)
        let clickMapData = this._MinesNowMapDatas.find(e => e.userId === userId)

        //保存注单信息
        //更新金额
        this.saveUserDataMines(userId, index)

        //自动结算返回
        this.sendToUser(userInfo.userId, 'CashOutMines',
            {
                index: betInfo.index,
                multiple: betInfo.multiple,
                cashOut: reward,
                coin: userInfo.coin,
                mapData: mapData.map,
                clickMapData: clickMapData.map,
                userName: userInfo.username,
                finishTime: betInfo.finishTime,
                amount: betInfo.betCount,
                payout: (betInfo.betCount * betInfo.multiple),
                profit: betInfo.multiple,
                auto: auto//是否自动下注
            })

        //广播注单记录 
        this.sendToAll('HistoryLog', {
            index: index,
            userName: userInfo.username.replace(/(\w{1}).*(\w{1})/, "$1****$2"),
            finishTime: betInfo.finishTime,
            amount: betInfo.betCount,
            payout: (betInfo.betCount * betInfo.multiple),
            profit: betInfo.multiple,
            mapData: mapData.map,
            clickMapData: clickMapData.map,
            auto: auto//是否自动下注
        })

        //手动结算返回
        return {
            success: true, index: betInfo.index, multiple: betInfo.multiple,
            cashOut: reward, coin: userInfo.coin, mapData: mapData.map, clickMapData: clickMapData.map, auto: 0//是否自动下注
        }
    }

    public async EndMine(userId: number, index: number) {
        //改变状态
        this._status = STATUS.RESULT

        //清空注单
        let betInfoIndex = this._betInfos.findIndex(e => e.userId === userId && e.index === index)
        this._betInfos.splice(betInfoIndex, 1)
    }

    private async saveUserDataMines(userId: number, index: number) {
        //修改金额
        const userInfo = this._userInfos.find(e => e.userId === userId)
        this._userService.updateUserCoin(userId, userInfo.coin)
        // 用户下注记录入库   游戏记录入库
        this._recordService.saveBetRecordMines(this._betInfos, this._userInfos, this._roundId, userId, this._MinesMapDatas, this._MinesNowMapDatas)

    }




    // 机器人下注
    public robotBet(robotBetInfo: any): boolean {
        if (this._status !== STATUS.WAIT) {
            return false
        }
        if (robotBetInfo.betCount === 0) {
            return false
        }
        robotBetInfo.betTime = new Date()
        robotBetInfo.finishTime = new Date()
        robotBetInfo.beforCoin = 0
        robotBetInfo.afterCoin = 0

        this._robotBetInfos.push(robotBetInfo)
        this.sendToAll('bet', { userId: robotBetInfo.userId, index: robotBetInfo.index, username: robotBetInfo.username, betCount: robotBetInfo.betCount })
        return true
    }

    // 退出订单
    public cancel(userId: number, index: number): boolean {
        let betInfoIndex = this._betInfos.findIndex(e => e.userId === userId && e.index === index)
        if (betInfoIndex < 0) {
            return false
        }
        const betInfo = this._betInfos[betInfoIndex]
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (!userInfo) {
            return false
        }
        const betCount = betInfo.betCount
        this._betInfos.splice(betInfoIndex, 1)
        userInfo.coin += betCount
        this.sendToAll('cancel', { userId: userId, index: index })
        return true
    }
    //
    public cashOut(userId: number, index: number): { success: boolean, multiple: number, cashOut: number } {
        const betInfo = this._betInfos.find(e => e.userId === userId && e.index === index)
        if (!betInfo) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        if (this._syncIndex === 0) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        const multiple = this._syncPoints[this._syncIndex - 1]
        if (betInfo.auto && multiple > betInfo.multiple) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        //小于101不能cash out
        if (multiple < 101) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        if (betInfo.cashOut) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (!userInfo) {
            return { success: false, multiple: 0, cashOut: 0 }
        }
        betInfo.cashOut = true
        betInfo.multiple = multiple
        betInfo.finishTime = new Date()
        const reward = betInfo.betCount * betInfo.multiple / 100
        userInfo.coin += reward
        betInfo.afterCoin = userInfo.coin
        this.sendToAll('cashOut', { userId: userId, index: index, multiple: multiple, cashOut: reward })
        return { success: true, multiple: multiple, cashOut: reward }
    }





    private autoCashOut() {
        if (this._syncIndex === 0) {
            return
        }
        if (this._syncPoints[this._syncIndex - 1] < 101) {
            return
        }
        // 自动结算
        this._betInfos.forEach(e => {
            if (e.cashOut) {
                return
            }
            if (!e.auto) {
                return
            }
            if (this._syncPoints[this._syncIndex - 1] >= e.multiple) {
                e.cashOut = true
                const userInfo = this._userInfos.find(u => u.userId === e.userId)
                if (!userInfo) {
                    return
                }
                const reward = e.betCount * e.multiple / 100
                userInfo.coin += reward
                e.afterCoin = userInfo.coin
                e.finishTime = new Date()

                this.sendToUser(userInfo.userId, 'autoCashOut', { index: e.index, multiple: e.multiple, cashOut: reward, coin: userInfo.coin })
                this.sendToAll('cashOut', { userId: userInfo.userId, index: e.index, multiple: e.multiple, cashOut: reward })
            }
        })
        this._robotBetInfos.forEach(e => {
            if (e.cashOut) {
                return
            }
            if (this._syncPoints[this._syncIndex - 1] >= e.multiple) {
                e.cashOut = true
                const reward = e.betCount * e.multiple / 100
                e.afterCoin = reward;
                e.finishTime = new Date()
                this.sendToAll('cashOut', { userId: e.userId, index: e.index, multiple: e.multiple, cashOut: reward })
            }
        })
    }

    //查询渠道id是否存在，存在返回id，不存在返回-1
    public async haveUserBychannelId(channel_id: string) {
        // 查询channel_id是否存在 返回uid
        return await this._userService.haveUserBychannelId(channel_id)
    }

    //创建用户返回id
    public async createUserReturnId(values: any) {
        // 创建用户返回id
        return await this._userService.createUserReturnId(values)
    }
    //游戏地址
    public async getGameClientUrl() {
        return await this._userService.getGameClientUrl()
    }

    // 断线
    public async offline(userId: number) {
        // 加载数据中玩家掉线，直接解锁玩家数据
        const lIndex = this._loading.findIndex(e => e === userId)
        if (lIndex >= 0) {
            await this.unlockCoin(userId)
            this._loading.splice(lIndex, 1)
            return
        }
        // 先查找玩家
        const index = this._userInfos.findIndex(e => e.userId === userId)
        if (index < 0) {
            return
        }
        const userInfo = this._userInfos[index]

        this._channel.removeMember(userId.toString())
        if (this._status === STATUS.GAME) {
            let betTimes = this.betTimes(userId)
            if (betTimes > 0) {
                userInfo.offline = true
            } else {
                this._userInfos.splice(index, 1)
                await this.unlockCoin(userId)
                this.noticeOffline([{ username: userInfo.username, channelId: userInfo.channelId }])
            }
        } else if (this._status === STATUS.WAIT) {
            // 取消所有的订单
            this.cancel(userId, 1)
            this.cancel(userId, 2)
            this._userInfos.splice(index, 1)
            await this.updateCoinAndUnlock(userInfo.userId, userInfo.coin)
            this.noticeOffline([{ username: userInfo.username, channelId: userInfo.channelId }])
        } else {
            this._userInfos.splice(index, 1)
            await this.unlockCoin(userId)
            this.noticeOffline([{ username: userInfo.username, channelId: userInfo.channelId }])
        }
    }
    //  获取游戏状态
    public get status() {
        return this._status;
    }
    //  获取游戏局号
    public get roundId() {
        return this._roundId;
    }
    // 开始
    private onStart() {
        if (!this._running) {
            return
        }
        //可以下注了
        this._status = STATUS.WAIT
        this._roundId = date2Number(new Date())
        this._statusTime = Date.now()
        this._betInfos = []
        this._robotBetInfos = []
        this.sendToAll('status', { status: this._status, duration: 6000, roundId: this._roundId })
        const controls = this.app.getServersByType("control")
        this.app.rpc.control.remote.robotBet.toServer(controls[0].id, 6000)
        this._statusJob = scheduleJob({
            start: Date.now() + 6000,
        }, () => {
            this.onGame()
        })
    }
    // 游戏
    private onGame() {
        //同步飞机数值
        this._status = STATUS.GAME
        this._statusTime = Date.now()
        this.sendToAll('status', { status: this._status })
        const result = this._controlConfig.finished() ? randResult(1) : randResult(this._controlConfig.rebate)
        this._syncPoints = smooth(result)
        this._syncIndex = 0
        //每秒同步5次(200ms同步一次)
        this._statusJob = scheduleJob({
            start: Date.now() + 100,
            period: 200,
            count: this._syncPoints.length
        }, () => {
            if (this._gameConfig.gameMaxMultiple > 0 && this._syncPoints[this._syncIndex] >= this._gameConfig.gameMaxMultiple) {
                if (!this.cashFull()) {
                    this._syncPoints.splice(this._syncIndex)
                    this._syncPoints[this._syncIndex] = randomNumber(this._syncPoints[this._syncIndex - 1], this._gameConfig.gameMaxMultiple)
                }
            }
            const multiple = this._syncPoints[this._syncIndex]
            this._syncIndex += 1
            // 还是和cash out保持一致
            this.autoCashOut()
            // 发送结算信息
            this.sendToAll('syncPoint', { multiple: multiple })
            if (this._syncIndex == this._syncPoints.length) {
                cancelJob(this._statusJob)
                this.onEnd(0, 0)
            }
        })
    }

    private cashFull() {
        return !(this._betInfos.find(e => e.cashOut === false))
    }

    public getUserInfo(userId: number) {
        return this._userInfos.find(e => e.userId === userId)
    }

    public getbetInfo(userId: number) {
        return this._betInfos.find(e => e.userId === userId)
    }

    public async forceEnd(roundId: number, operatorId: number): Promise<boolean> {
        cancelJob(this._statusJob)
        await this.onEnd(1, operatorId)
        return true
    }

    // 结束
    private async onEnd(endType: number, operatorId: number) {
        this._status = STATUS.RESULT
        this._history.push(this._syncPoints[this._syncIndex - 1])
        if (this._history.length > 30) {
            this._history.splice(0, this._history.length - 30)
        }
        this._statusTime = Date.now()
        this.sendToAll('status', { status: this._status, duration: 3000 })
        // 保存所有下过注的玩家金额
        const userIds = await this.saveUserData()
        // 查询渠道回调信息
        this.notice(userIds)
        const roundReward = await this.getRoundReward()
        let saveTask = [this.saveGameRecord(endType, operatorId, roundReward).catch(err => { logger.warn(err) }),
        this.saveBetRecord().catch(err => logger.warn(err)),
        this.saveProgress(roundReward).catch(err => logger.warn(err))]
        // 踢掉不在线玩家
        let unlockTask = []
        let notices = []
        for (let i = this._userInfos.length - 1; i >= 0; i--) {
            if (this._userInfos[i].offline) {
                const userId = this._userInfos[i].userId
                notices.push({
                    username: this._userInfos[i].username,
                    channelId: this._userInfos[i].channelId
                })
                this._userInfos.splice(i, 1)
                unlockTask.push(this.unlockCoin(userId))
            }
        }
        if (unlockTask.length > 0) {
            await Promise.all(unlockTask)
        }
        this.noticeOffline(notices)
        await Promise.all(saveTask)
        this._statusJob = scheduleJob({
            start: Date.now() + 3000
        }, () => {
            this.onStart()
        })
    }

    private async unlockCoin(userId: number) {
        this._unloading.push(userId)
        this._userService.unlockCoinById(userId).catch(err => {
            logger.warn('unlock fail: ' + userId + ' error:' + err)
        }).finally(() => {
            const index = this._unloading.findIndex(e => e === userId)
            this._unloading.splice(index, 1)
        })
    }

    private async notice(userIds: number[]) {
        if (!this._setting.notice) {
            return
        }
        let channelInfos = []
        try {
            channelInfos = await this._userService.getChannelInfos()
        } catch (err) {
            logger.warn('get channel info fail' + err)
            return
        }
        https.get[promisify.custom] = function (url) {
            return new Promise((resolve, reject) => {
                https.get(url, (res) => {
                    res.statusCode === 200 ? resolve(res.statusMessage) : reject(res.statusMessage)
                })
            })
        }
        const aget = promisify(https.get)
        let task = []
        userIds.forEach(e => {
            const userInfo = this._userInfos.find(e1 => e1.userId === e)
            if (!userInfo) {
                return
            }
            const channelInfo = channelInfos.find(e2 => e2.channelId === userInfo.channelId)
            if (!channelInfo || !channelInfo.notifyUrl) {
                return;
            }
            task.push(aget(channelInfo.notifyUrl + '?' + 'username=' + userInfo.username))
        })
        if (task.length > 0) {
            Promise.all(task).catch(e => {
                logger.warn('notice fail')
            })
        }
    }

    private async noticeOffline(userInfos: { username: string, channelId: number }[]) {
        if (!this._setting.notice) {
            return
        }
        let channelInfos = []
        try {
            channelInfos = await this._userService.getChannelInfos()
        } catch (err) {
            logger.warn('get channel info fail' + err)
            return
        }
        https.get[promisify.custom] = function (url) {
            return new Promise((resolve, reject) => {
                https.get(url, (res) => {
                    if (res.statusCode !== 200) {
                        reject(res.statusMessage)
                        return
                    }
                    res.on("data", (data) => {
                        let obj = JSON.parse(data.toString())
                        if (obj.code === 1) {
                            resolve('success')
                        } else {
                            reject('error code')
                        }
                    })
                }).on('error', err => {
                    reject(err)
                })
            })
        }
        const aget = promisify(https.get)
        let task = []
        userInfos.forEach(e => {
            const channelInfo = channelInfos.find(e1 => e.channelId === e1.channelId)
            if (!channelInfo || !channelInfo.offlineUrl) {
                return;
            }
            task.push(aget(channelInfo.offlineUrl + '?' + 'username=' + e.username).catch(err => {
                logger.warn(e.username + ' notice offline fail: ' + err)
            }))
        })
        if (task.length > 0) {
            Promise.all(task).catch(e => {
                logger.warn('notice offline fail')
            })
        }
    }

    private async saveUserData(): Promise<number[]> {
        // 保存所有下过注的玩家金额
        let saveCoin = []
        this._betInfos.forEach(e => {
            if (saveCoin.includes(e.userId)) {
                return
            }
            saveCoin.push(e.userId)
        })
        let task = []
        saveCoin.forEach(e => {
            const userInfo = this._userInfos.find(e1 => e1.userId === e)
            task.push(this._userService.updateUserCoin(e, userInfo.coin).catch(err => {
                logger.warn(err)
                if (!userInfo.offline) {
                    this.akickByUserId(userInfo.userId, 'kick by coin update fail')
                }
            }))
        })
        if (task.length > 0) {
            await Promise.all(task)
        }
        return saveCoin
    }

    // 用户下注记录入库
    private async saveBetRecord() {
        if (this._betInfos.length === 0 && this._robotBetInfos.length === 0) {
            return
        }
        return await this._recordService.saveBetRecord(this._betInfos, this._userInfos, this._roundId, this._robotBetInfos)
    }

    // 游戏记录入库
    private async saveGameRecord(endType: number, operatorId: number, roundReward: number) {
        return this._recordService.saveGameRecord(roundReward, this._roundId, this._syncPoints[this._syncIndex - 1], endType, operatorId)
    }
    //
    private async saveProgress(progress: number) {
        if (this._controlConfig.finished()) {
            return true
        }
        return await this._controlConfig.saveProgress(progress)
    }
    // 计算当前局输赢
    private async getRoundReward() {
        let progress = 0
        this._betInfos.forEach(e => {
            e.cashOut ? progress -= (e.betCount * e.multiple / 100 - e.betCount) : progress += e.betCount
        })
        return progress
    }
    public async akickByUserId(userId: number, reason: string) {
        const connector = this.app.getServersByType('connector')
        let backendSessionService = this.app.backendSessionService
        let sessions: BackendSession[] = await backendSessionService.agetByUid(connector[0].id, userId.toString())
        // 玩家不在线
        if (!sessions || sessions.length !== 1) {
            return
        }
        await backendSessionService.akickByUid(connector[0].id, userId.toString(), reason)
    }
    // 提出所有玩家
    public async kickAll() {
        let task = []
        this._userInfos.forEach(e => {
            task.push(this.akickByUserId(e.userId, 'kick by close server'))
        })
        return await Promise.all(task)
    }
    // 强制提出所有的玩家，关服的时候使用
    public async forceKickAll(): Promise<any[]> {
        let task = []
        this._userInfos.forEach(e => {
            task.push(this._userService.unlockCoinById(e.userId))
        })
        // 清空所有的注单和用户信息
        this._betInfos = []
        this._userInfos = []
        if (task.length > 0) {
            return await Promise.all(task)
        }
        return []
    }

    public async increaseCoin(userId: number, coin: number): Promise<boolean> {
        if (this._updating.includes(userId)) {
            return false
        }
        const userInfo = this._userInfos.find(e => e.userId === userId)
        try {
            this._updating.push(userId)
            if (!await this._userService.updateCoin(userId, userInfo.coin, coin)) {
                return false  // 加币失败
            }
        } finally {
            const index = this._updating.indexOf(userId)
            this._updating.splice(index, 1)
        }
        userInfo.coin += coin
        this.sendToUser(userId, 'syncProp', { coin: userInfo.coin })
        return true  // 成功
    }

    public async decreaseCoin(userId: number, coin: number): Promise<boolean> {
        if (this._updating.includes(userId)) {
            return false
        }
        const userInfo = this._userInfos.find(e => e.userId === userId)
        if (coin > userInfo.coin) {
            return false
        }
        try {
            this._updating.push(userId)
            if (!await this._userService.decreaseCoin(userId, userInfo.coin, -coin)) {
                return false
            }
        } finally {
            const index = this._updating.indexOf(userId)
            this._updating.splice(index, 1)
        }
        userInfo.coin -= coin
        if (coin === 0) {
            userInfo.coin = 0
        }
        this.sendToUser(userId, 'syncProp', { coin: userInfo.coin })
        return true
    }

    private async updateCoinAndUnlock(userId: number, coin: number) {
        this._unloading.push(userId)
        this._updating.push(userId)
        this._userService.updateCoinAndUnlock(userId, coin).catch(err => {
            logger.warn('unlock fail: ' + userId + ' error:' + err)
        }).finally(() => {
            const index = this._unloading.findIndex(e => e === userId)
            if (index >= 0) {
                this._unloading.splice(index, 1)
            }
            const index2 = this._updating.findIndex(e => e === userId)
            if (index2 >= 0) {
                this._updating.splice(index2, 1)
            }
        })
    }
}
